﻿namespace Grundy.Net

open System
open System.Text
open System.Net
open System.Net.Sockets
open System.Threading

type IMessageHandler =
    abstract member ProcessMessage : string -> EndPoint -> string

type TcpServer(ip, port, buffer_size:int, message_handler:IMessageHandler) =        
    let mutable isStarted:bool = false
    let semaphore = new Semaphore(0, 4)
    let connectedSockets = ref 0
    let listener = new TcpListener(ip,port) 
    let logger = log4net.LogManager.GetLogger(typeof<TcpServer>)
    
    member x.Started 
        with get() = isStarted

    member x.ConnectedSockets
        with get() = connectedSockets.Value
        
    member x.Start() = async { x.Run() } |> Async.Start
    member x.Shutdown() = 
        try
            listener.Stop()
            isStarted <- false
        with e ->
            logger.ErrorFormat("Could not stop TCP Server.")     
    member x.Run() =
        try
            listener.Start()
            isStarted <- true

            while true do
            let cep = ref<EndPoint> null
            try
                let client = listener.AcceptTcpClient()
                Interlocked.Increment(&connectedSockets.contents) |> ignore
                cep := client.Client.RemoteEndPoint
                async{      
                    Async.SwitchToThreadPool() |> ignore
                    try     
                        let stream = client.GetStream();                
                        let incomingBuffer = Array.zeroCreate<byte>(buffer_size)
                        let incomingMessageSize = ref 0
                        let bytesRead = ref 0
                        let! tmp = stream.AsyncRead(incomingBuffer)
                        bytesRead := tmp
                        incomingMessageSize.Value <- bytesRead.Value
                        while bytesRead.Value > 0 && incomingBuffer.[incomingMessageSize.Value - 1] <> (byte)4 && incomingMessageSize.Value <= buffer_size do
                            let tmpBuffer = Array.zeroCreate<byte>(8192)
                            let! tmp = stream.AsyncRead(tmpBuffer)
                            Array.Copy(tmpBuffer, 0, incomingBuffer, incomingMessageSize.Value, tmp)
                            bytesRead.Value <- tmp
                            incomingMessageSize.Value <- incomingMessageSize.Value + tmp
                        let incomingMessage = UTF8Encoding.UTF8.GetString(incomingBuffer, 0, incomingMessageSize.Value - 1)
                        logger.InfoFormat("Received incoming message {0} from {1}.", incomingMessage, cep.Value)
                        let outgoingMessage = message_handler.ProcessMessage incomingMessage cep.Value
                        logger.InfoFormat("Sent outgoing to {0} from {1}.", outgoingMessage, cep.Value)
                        let outgoingBuffer = Array.append (UTF8Encoding.UTF8.GetBytes(outgoingMessage)) [|(byte) 4|]
                        let! u = stream.AsyncWrite(outgoingBuffer, 0, outgoingBuffer.Length)                    
                        Interlocked.Decrement(&connectedSockets.contents) |> ignore
                        client.Close()
                    with e -> 
                        Interlocked.Decrement(&connectedSockets.contents) |> ignore
                        logger.ErrorFormat("An error occurred in the TCP Server while communicating with {3}. Message: {1} {0}Details: {0}{2}",Environment.NewLine, e.Message, e.ToString(), cep.Value)
                        ()
                } |> Async.Start  
                
            with e -> 
                Interlocked.Decrement(&connectedSockets.contents) |> ignore
                logger.ErrorFormat("An error occurred in the TCP Server while communicating with {3}. Message: {1} {0}Details: {0}{2}",Environment.NewLine, e.Message, e.ToString(), cep.Value)
                ()
        with e ->
            logger.ErrorFormat("Could not start TCP Server for IP: {1} and Port: {2}. Message: {3} {0}Details: {0}{4}",Environment.NewLine, ip, port, e.Message, e.ToString)            
            ()
        
